/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 - 2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/

package com.genia.toolbox.spring.scope;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

/**
 * Special scope to handle case where session and request scope are not
 * available.
 */
public class ScopeWithFallback
    implements Scope
{

  /**
   * mainScope to use.
   */
  private Scope mainScope;

  /**
   * fallback scope to use if the main failed.
   */
  private Scope fallbackScope;



  /**
   * Return the object with the given name from the underlying scope,
   * {@link org.springframework.beans.factory.ObjectFactory#getObject() creating it}
   * if not found in the underlying storage mechanism.
   * <p>
   * This is the central operation of a Scope, and the only operation that is
   * absolutely required.
   * 
   * @param name
   *          the name of the object to retrieve
   * @param objectFactory
   *          the {@link ObjectFactory} to use to create the scoped object if it
   *          is not present in the underlying storage mechanism
   * @return the desired object (never <code>null</code>)
   */
  public Object get(String name, ObjectFactory objectFactory)
  {
    try {
      return getMainScope().get(name, objectFactory);
    }
    catch (Exception e) {
      // Do fallback
      return getFallbackScope().get(name, objectFactory);
    }
  }



  /**
   * Return the <em>conversation ID</em> for the current underlying scope, if
   * any.
   * <p>
   * The exact meaning of the conversation ID depends on the underlying storage
   * mechanism. In the case of session-scoped objects, the conversation ID would
   * typically be equal to (or derived from) the
   * {@link javax.servlet.http.HttpSession#getId() session ID}; in the case of
   * a custom conversation that sits within the overall session, the specific ID
   * for the current conversation would be appropriate.
   * <p>
   * <b>Note: This is an optional operation.</b> It is perfectly valid to
   * return <code>null</code> in an implementation of this method if the
   * underlying storage mechanism has no obvious candidate for such an ID.
   * 
   * @return the conversation ID, or <code>null</code> if there is no
   *         conversation ID for the current scope
   */
  public String getConversationId()
  {
    try {
      return getMainScope().getConversationId();
    }
    catch (Exception e) {
      // Do fallback
      return getFallbackScope().getConversationId();
    }
  }



  /**
   * getter for the fallbackScope property.
   * 
   * @return the fallbackScope
   */
  public Scope getFallbackScope()
  {
    return fallbackScope;
  }



  /**
   * getter for the mainScope property.
   * 
   * @return the mainScope
   */
  public Scope getMainScope()
  {
    return mainScope;
  }



  /**
   * Register a callback to be executed on destruction of the specified object
   * in the scope (or at destruction of the entire scope, if the scope does not
   * destroy individual objects but rather only terminates in its entirety).
   * <p>
   * <b>Note: This is an optional operation.</b> This method will only be
   * called for scoped beans with actual destruction configuration
   * (DisposableBean, destroy-method, DestructionAwareBeanPostProcessor).
   * Implementations should do their best to execute a given callback at the
   * appropriate time. If such a callback is not supported by the underlying
   * runtime environment at all, the callback <i>must be ignored and a
   * corresponding warning should be logged</i>.
   * <p>
   * Note that 'destruction' refers to to automatic destruction of the object as
   * part of the scope's own lifecycle, not to the individual scoped object
   * having been explicitly removed by the application. If a scoped object gets
   * removed via this facade's {@link #remove(String)} method, any registered
   * destruction callback should be removed as well, assuming that the removed
   * object will be reused or manually destroyed.
   * 
   * @param name
   *          the name of the object to execute the destruction callback for
   * @param callback
   *          the destruction callback to be executed. Note that the passed-in
   *          Runnable will never throw an exception, so it can safely be
   *          executed without an enclosing try-catch block. Furthermore, the
   *          Runnable will usually be serializable, provided that its target
   *          object is serializable as well.
   * @see org.springframework.beans.factory.DisposableBean
   * @see org.springframework.beans.factory.support.AbstractBeanDefinition#getDestroyMethodName()
   * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor
   */
  public void registerDestructionCallback(String name, Runnable callback)
  {
    try {
      getMainScope().registerDestructionCallback(name, callback);
    }
    catch (Exception e) {
      // Do fallback
      getFallbackScope().registerDestructionCallback(name, callback);
    }
  }



  /**
   * Remove the object with the given <code>name</code> from the underlying
   * scope.
   * <p>
   * Returns <code>null</code> if no object was found; otherwise returns the
   * removed <code>Object</code>.
   * <p>
   * Note that an implementation should also remove a registered destruction
   * callback for the specified object, if any. It does, however, <i>not</i>
   * need to <i>execute</i> a registered destruction callback in this case,
   * since the object will be destroyed by the caller (if appropriate).
   * <p>
   * <b>Note: This is an optional operation.</b> Implementations may throw
   * {@link UnsupportedOperationException} if they do not support explicitly
   * removing an object.
   * 
   * @param name
   *          the name of the object to remove
   * @return the removed object, or <code>null</code> if no object was present
   * @see #registerDestructionCallback
   */
  public Object remove(String name)
  {
    try {
      return getMainScope().remove(name);
    }
    catch (Exception e) {
      // Do fallback
      return getFallbackScope().remove(name);
    }
  }



  /**
   * setter for the fallbackScope property.
   * 
   * @param fallbackScope
   *          the fallbackScope to set
   */
  public void setFallbackScope(Scope fallbackScope)
  {
    this.fallbackScope = fallbackScope;
  }



  /**
   * setter for the mainScope property.
   * 
   * @param mainScope
   *          the mainScope to set
   */
  public void setMainScope(Scope mainScope)
  {
    this.mainScope = mainScope;
  }

}
